/*
 * Copyright 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using Org.BouncyCastle.Utilities.Encoders;

namespace Bitcoin.Cryptography
{
    /// <summary>Represents a SHA256 hash value, which is essentially juat a byte array.</summary>
    /// <remarks>
    /// A Sha256Hash just wraps a byte[] so that Equals and GetHashCode() work correctly, allowing it to be used as keys in a
    /// dictionary. It also checks that the length is correct and provides a bit more type safety.
    /// </remarks>
    public class Sha256Hash
    {
        private readonly byte[] _bytes;

        public static readonly Sha256Hash ZeroHash = new Sha256Hash(new byte[32]);

        /// <summary>
        /// Creates a Sha256Hash by wrapping the given byte array. It must be 32 bytes long.
        /// </summary>
        public Sha256Hash(byte[] bytes)
        {
            if (bytes.Length != 32) {  throw new ArgumentException("Argument is wrong length for SHA256 hash.", "bytes"); }
            _bytes = bytes;
        }

        /// <summary>
        /// Creates a Sha256Hash by decoding the given hex s. It must be 64 characters long.
        /// </summary>
        public Sha256Hash(string s)
        {
            if (s.Length != 64) { throw new ArgumentException("Argument is wrong length for SHA256 hex string.", "s"); }
            _bytes = Hex.Decode(s);
        }

        /// <summary>
        /// Returns true if the hashes are equal.
        /// </summary>
        public override bool Equals(object other)
        {
            if (!(other is Sha256Hash)) return false;
            return _bytes.SequenceEqual(((Sha256Hash) other)._bytes);
        }

        /// <summary>
        /// Hash code of the byte array as calculated by <see cref="object.GetHashCode"/>. Note the difference between a SHA256
        /// secure bytes and the type of quick/dirty bytes used by the Java hashCode method which is designed for use in
        /// bytes tables.
        /// </summary>
        public override int GetHashCode()
        {
            return _bytes != null ? _bytes.Aggregate(1, (current, element) => 31*current + element) : 0;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <remarks>
        /// Bitcoin convention is to treat the hash as a BigInteger (little endian), which for block
        /// difficulty needs a number of leading zeros (which would be ending zeros in the array).
        /// e.g. for block 1 in production chain the hash is "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"
        /// </remarks>
        /// <returns></returns>
        public override string ToString()
        {
            //return Hex.ToHexString(_bytes.Reverse().ToArray());
            return Hex.ToHexString(_bytes);
        }

        /// <summary>
        /// Returns the bytes interpreted as a positive integer.
        /// </summary>
        public BigInteger ToBigInteger()
        {
            var temp = _bytes.Concat(new byte[] {0}).ToArray();
            //var temp = _bytes.Reverse().Concat(new Byte[] {0}).ToArray();
            // Note: Numerics BigInteger parses in little endian format
            var value = new BigInteger(temp);
            return value;
        }

        public byte[] Bytes
        {
            get { return _bytes; }
        }

        public Sha256Hash Clone()
        {
            return new Sha256Hash(_bytes);
        }
    }
}